#include "BlipController.h"
#include "SuperString.h"
#include <Windows.h>
#include <ctime>

//NEAT
inline double fastPow(double a, double b) {
  union {
    double d;
    int x[2];
  } u = { a };
  u.x[1] = (int)(b * (u.x[1] - 1072632447) + 1072632447);
  u.x[0] = 0;
  return u.d;
}

BlipController::BlipController( int num, int width, int height, int refresh,
							   float gravity, float field, HWND * hwnd )
		: m_grav( gravity ), m_field( field ), m_numBlips(num),
		  m_width( width ), m_height( height ), m_hwnd( hwnd ), m_refresh(refresh)
{
	m_busyResizing = false;
	m_threadsMutex = nullptr;
	m_staticColor = false;
	m_fastestBlip = 0;
	m_redOff = 16;
	m_greOff = 8;
	m_bluOff = 0;

	int x(0), y(0);
	m_fps = 0;
	FPOINT xy;
	SYSTEM_INFO sysinfo;
	GetSystemInfo( &sysinfo );

	m_red = m_blue = m_green = 0x20;

	m_numThreads = sysinfo.dwNumberOfProcessors;

	int blipsPerThread = num / m_numThreads;
	m_numBlips = blipsPerThread * m_numThreads;
	m_blips = new Blip[m_numBlips];

	m_bitmapWorkers = new thread[m_numThreads];

	RebuildBlipPositions();

	m_killThreads = false;
	m_pull = false;

	HDC temp_hdc = CreateCompatibleDC(NULL);

	BITMAPINFO bi;

	ZeroMemory(&bi, sizeof(BITMAPINFO));
	bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bi.bmiHeader.biWidth = m_width;
	bi.bmiHeader.biHeight = -m_height;  //negative so (0,0) is at top left
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;

	m_hbitmap = new HBITMAP( CreateDIBSection( temp_hdc, &bi, DIB_RGB_COLORS, (void **)(&m_bitmapbits), NULL, NULL ) );
	m_hbitmapRet = new HBITMAP( CreateBitmap( width, height, 1, 32, NULL ) );

	DeleteObject(temp_hdc);

	m_bitmapThread = thread( &BlipController::BuilderWrapper, this );

	m_thread = new thread[m_numThreads];
	m_threadsMutex = new mutex[m_numThreads];

	for ( int i = 0; i < m_numThreads; i++ )
	{
		CreateWorker( m_blips + i * blipsPerThread, blipsPerThread, i );
	}
}

void BlipController::CreateWorker( Blip * blips, int blips_num, int worker_num )
{
	m_thread[worker_num] = thread( &BlipController::WorkerWrapper, this, (void *)( blips ), (void *)(blips_num), (void *)(worker_num) );
}

BlipController::~BlipController()
{
	m_killThreads = true;
	for ( int i = 0; i < m_numThreads; )
	{
		//m_threadsMutex[i].lock();
		if ( m_thread[i].joinable() )
		{
			m_thread[i].join();
			i++;
		}
		else
			Sleep(5);
	}

	//for ( int i = 0; i < m_numThreads; i++ )
		//m_threadsMutex[i].unlock();

	delete [] m_thread;
	delete [] m_bitmapWorkers;
	DeleteObject( *m_hbitmap );
	DeleteObject( *m_hbitmapRet );
	delete m_hbitmap;
	delete m_hbitmapRet;
}

void BlipController::Worker( Blip * blips, int num, int worker )
{
	float		tempd, tempx(0), tempy(0);
	double		start, end;
	int			sleep_time;
	POINT		mouse;
	FPOINT		temp;

	if ( worker > m_numThreads || worker < 0 )
		return;

	//m_threadsMutex[worker].lock();

	while ( !m_killThreads )
	{
		start = clock();

		for (int i = 0; ( i < num ) && !m_killThreads; i++ )
		{
			mouse = m_well;
			mouse.x -= m_off.x;
			mouse.y -= m_off.y;
			if ( m_pull )
			{
				tempx = mouse.x - blips[i].GetPoint().x;
				tempy = mouse.y - blips[i].GetPoint().y;
				tempd = tempx * tempx;
				tempd += tempy * tempy;
				tempd = sqrt( tempd );	//tempd contains force vector
				//Force (AKA Inverse of the Hypotenuse ) * ( adj / hyp )
				tempx = ( m_grav / fastPow((double)tempd, (double)1.0/m_field ) ) * ( tempx / tempd );
				tempy = ( m_grav / fastPow((double)tempd, (double)1.0/m_field ) ) * ( tempy / tempd );
			}
			else 
			{
				tempx = -blips[i].GetVelocityX().GetVelocity() * .05;
				tempy = -blips[i].GetVelocityY().GetVelocity() * .05;
			}
			blips[i].SetAccelerationY( Acceleration( tempy ) );
			blips[i].SetAccelerationX( Acceleration( tempx ) );

			temp = blips[i].GetPoint();
			
			blips[i].Tick();

			if ( m_clipping )
			{
				if( temp.x > m_width )
				{
					temp.x -= m_width;
					blips[i].SetPoint(temp);
				}
				else if( temp.x < 0 )
				{
					temp.x += m_width;
					blips[i].SetPoint(temp);
				}
			
				if( temp.y > m_height )
				{
					temp.y -= m_height;
					blips[i].SetPoint(temp);
				}
				else if( temp.y < 0 )
				{
					temp.y += m_height;
					blips[i].SetPoint(temp);
				}
			}

		}
		//Result should be time in milliseconds
		end = ( clock() - start );
		sleep_time = m_refresh - end;

		if ( !m_killThreads )
		{
			if ( sleep_time > 0 )
				Sleep( sleep_time );
		}
	}
	//m_threadsMutex[worker].unlock();
}

HBITMAP * BlipController::Tick()
{
	m_bitmapThread.join();

	if ( m_fps = 0 )
		m_fps = clock();
	m_fps = clock() - m_fps;

	HDC tempHDC = CreateCompatibleDC( NULL );
	HDC tempHDCRet = CreateCompatibleDC( NULL );

	SelectObject( tempHDCRet, *m_hbitmapRet );
	SelectObject( tempHDC, *m_hbitmap );
	BitBlt( tempHDCRet, 0, 0, m_width, m_height, tempHDC, 0, 0, SRCCOPY );
	DeleteObject( tempHDCRet );
	DeleteObject( tempHDC );

	m_bitmapThread = thread( &BlipController::BuilderWrapper, this );

	return m_hbitmapRet;
}

void BlipController::BuildNewBitmap()
{
	HRGN hrgn;
	RECT rect;
	HBRUSH hbrush;	
	HDC hdc = CreateCompatibleDC(NULL);

	SuperString ss;
	static bool	rd(true), gd(true), bd(true);
	int x, y;

	if ( !m_busyResizing )
	{
		m_fastestBlip = 0.0;

		rect.left = rect.top = 0;
		rect.right = m_width;
		rect.bottom = m_height;

		SelectObject( hdc, *m_hbitmap );
		SetBkColor( hdc, RGB(0,0,0) );
		hrgn = CreateRectRgnIndirect( &rect );
		hbrush = CreateSolidBrush( RGB(0,0,0) );
		FillRgn( hdc, hrgn, hbrush );

		if ( m_red == 0xFF )
			rd = false;
		else if ( m_red == 0x20 )
			rd = true;

		if ( m_green == 0xFF )
			gd = false;
		else if ( m_green == 0x20 )
			gd = true;

		if ( m_blue == 0xFF )
			bd = false;
		else if ( m_blue == 0x20 )
			bd = true;

		if ( rd )
			m_red++;
		else
			m_red--;
	
		if ( m_red % 4 == 0 )
		{
			if ( gd )
				m_green++;
			else
				m_green--;
			if ( m_green % 4 == 0 )
			{
				if ( bd )
					m_blue++;
				else
					m_blue--;
			}
		}

		GdiFlush();

		int blipsPerThread = m_numBlips / m_numThreads;

		for ( int i = 0; i < m_numThreads; i++ )
		{
			m_bitmapWorkers[i] = thread( BlipController::BitmapWriter, this, m_blips + ( blipsPerThread * i ), (void *)blipsPerThread );
		}

		for ( int i = 0; i < m_numThreads; i++ )
		{
			m_bitmapWorkers[i].join();
		}

		SetTextColor( hdc, RGB( m_red, m_green, m_blue ) );

		m_fps = (1000 / ( clock() - m_fps ) );

		ss.setString( "FPS: ", m_fps, " NUM_BLIPS: ", m_numBlips, 
			" Fastest Blip: ", m_fastestBlip );

		TextOut( hdc, 0, 0, ss.getString(), strlen(ss.getString()) );

		DeleteObject( hbrush );
		DeleteObject( hrgn );
		DeleteObject( hdc );
	} // if ( !m_busyResizing )
}

void BlipController::BitmapWorker( Blip * blips, int num )
{
	if ( num > m_numBlips || num < 0 )
		return;
	int x(0), y(0);
	COLORREF dynamic_rgb = RGB( m_blue, m_green, m_red );
	float tempd(0);
	for ( int i = 0; i < num; i++ )
	{
		unsigned char b(0x20), g(0x20), r(0x20);
		if ( m_busyResizing )
			break;
		x = blips[i].GetPoint().x;
		y = blips[i].GetPoint().y;
		if ( !m_staticColor )
		{
			b = 0xFF;
			r = 0x00;

			tempd = sqrt( pow(blips[i].GetVelocityX().GetVelocity(), 2 ) + pow( blips[i].GetVelocityY().GetVelocity(), 2) );
			if ( tempd > m_fastestBlip )
				m_fastestBlip = tempd;

			tempd =  tempd * 51.0;
			b -= (int)(tempd);
			r = (int)(tempd);
			if ( tempd >= 255.0 )
			{
				b = 0x000;
				r = 0xFF;
				g = tempd - 255.0;
				if ( tempd >= 510.0 )
				{
					g = 0xFF;
					b = tempd - 510.0;
					if ( tempd >= 765.0 )
						b = 0xFF;
				}
			}
			dynamic_rgb = r << m_redOff | g << m_greOff | b << m_bluOff;
		}
		if ( m_busyResizing )
			break;
		if ( x >= 0 && x < m_width )
			if ( y >= 0 && y < m_height )
				m_bitmapbits[(y * m_width) + x] |= dynamic_rgb;
	}
}


void BlipController::SetSize( int width, int height )
{
	m_bitmapThread.join();
	m_width = width;
	m_height = height;
	DeleteObject( *m_hbitmap );
	DeleteObject( *m_hbitmapRet );
	HDC temp_hdc = CreateCompatibleDC(NULL);

	BITMAPINFO bi;

	ZeroMemory(&bi, sizeof(BITMAPINFO));
	bi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bi.bmiHeader.biWidth = m_width;
	bi.bmiHeader.biHeight = -m_height;  //negative so (0,0) is at top left
	bi.bmiHeader.biPlanes = 1;
	bi.bmiHeader.biBitCount = 32;

	*m_hbitmap = CreateDIBSection( temp_hdc, &bi, DIB_RGB_COLORS, (void **)(&m_bitmapbits), NULL, NULL );

	DeleteObject(temp_hdc);
	*m_hbitmapRet = CreateBitmap( width, height, 1, 32, NULL );
	m_bitmapThread = thread( &BlipController::BuilderWrapper, this );
}

void BlipController::SetGravity( float gravity )
{
	m_grav = gravity;
}

void BlipController::SetField( float field )
{
	m_field = field;
}

void BlipController::SetPull( bool pull )
{
	m_pull = pull;
}

void BlipController::SetHWND( HWND * hwnd )
{
	m_hwnd = hwnd;
}

void BlipController::SetCTSOffset( POINT off )
{
	m_off = off;
}

void BlipController::Reset()
{
	m_killThreads = true;
	for ( int i = 0; i < m_numThreads; )
	{
		//m_threadsMutex[i].lock();
		if ( m_thread[i].joinable() )
		{
			m_thread[i].join();
			i++;
		}
		else
			Sleep(5);
	}

	//for ( int i = 0; i < m_numThreads; i++ )
		//m_threadsMutex[i].unlock();

	m_killThreads = false;
	m_pull = false;

	RebuildBlipPositions();

	m_thread = new thread[m_numThreads];

	for ( int i = 0; i < m_numThreads; i++ )
		CreateWorker( m_blips + (i * m_numBlips / m_numThreads), m_numBlips / m_numThreads, i );
}

void BlipController::UpdateWell( POINT well_point )
{
	m_well = well_point;
}

void BlipController::SetNumBlips( int blips )
{
	int x = 0;
	int i = 0;
	bool killing_threads = true;;

	if ( !m_busyResizing )
	{
		m_busyResizing = true;
		m_killThreads = true;
		
		for ( i = 0; i < m_numThreads; )
		{
			//m_threadsMutex[i].lock();
			if ( m_thread[i].joinable() )
			{
				m_thread[i].join();
				i++;
			}
			else
				Sleep(5);
		}

		//for ( int i = 0; i < m_numThreads; i++ )
			//m_threadsMutex[i].unlock();

		int blipsPerThread = blips / m_numThreads;
		m_numBlips = blipsPerThread * m_numThreads;

		delete [] m_blips;

		m_blips = new Blip[m_numBlips];

		RebuildBlipPositions();

		m_killThreads = false;

		for ( i = 0; i < m_numThreads; i++ )
			CreateWorker( m_blips + (i * m_numBlips / m_numThreads), m_numBlips / m_numThreads, i );
		m_busyResizing = false;
	}
}

void BlipController::RebuildBlipPositions()
{
	float x_off = 0;
	float y_off = 0;
	float ratio = (float)( m_width ) / m_height;
	float sqr_num = sqrt( m_numBlips );
	
	x_off = sqr_num * ratio;
	x_off = m_width / x_off;
	y_off = sqr_num * (1/ratio);
	y_off = m_height / y_off;

	FPOINT xy;
	xy.x = 0;
	xy.y = 0;
	for ( int i = 0; i < m_numBlips; i++ )
	{
		xy.x += x_off;

		if ( xy.x >= m_width )
		{
			xy.x = 0;
			xy.y += y_off;
		}
		m_blips[i].SetPoint(xy);
		m_blips[i].SetVelocityX( Velocity( 0, Acceleration(), 3) );
		m_blips[i].SetVelocityY( Velocity( 0, Acceleration(), 3) );
	}
}